from rest_framework import generics, status
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.permissions import IsAdminUser
from django.db import transaction
from .models import AccessLog, Device, VideoStreamToken
from .serializers import AccessLogSerializer, DeviceSerializer
from django.conf import settings
from channels.layers import get_channel_layer
from asgiref.sync import async_to_sync
import logging

logger = logging.getLogger(__name__)

class GenerateVideoStreamURL(APIView):
    """
    Generates a short-lived, single-use URL for accessing a video stream.
    This URL includes a token that the camera service will verify.
    """
    permission_classes = [IsAdminUser]

    def get(self, request, device_id: str, *args, **kwargs):
        try:
            device = Device.objects.get(device_id=device_id)
            camera_name = device.camera_service_name
            if not camera_name:
                return Response({"error": "Device has no associated camera service."}, status=status.HTTP_404_NOT_FOUND)

            camera_service_url = settings.CAMERA_SERVICES.get(camera_name)
            if not camera_service_url:
                return Response({"error": f"Camera service '{camera_name}' is not configured."}, status=status.HTTP_503_SERVICE_UNAVAILABLE)

            # Create a new one-time token
            token = VideoStreamToken.objects.create(user=request.user, device=device)

            # Construct the final URL for the client
            # This URL points directly to the camera service, with our token for verification
            final_url = f"{camera_service_url.rstrip('/')}/video_feed?token={token.id}"
            
            return Response({"url": final_url}, status=status.HTTP_200_OK)

        except Device.DoesNotExist:
            return Response({"error": "Device not found."}, status=status.HTTP_404_NOT_FOUND)
        except Exception as e:
            logger.error(f"Error generating video stream URL for device {device_id}: {e}", exc_info=True)
            return Response({"error": "An unexpected error occurred."}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

class VerifyVideoStreamToken(APIView):
    """
    An INTERNAL API endpoint for the camera service to verify a stream token.
    This should not be exposed to the public internet.
    """
    # This view uses a simple, internal-only secret key for authentication
    permission_classes = [] 

    def post(self, request, *args, **kwargs):
        token_id = request.data.get('token')
        internal_secret = request.headers.get('X-Internal-Secret')

        # Simple secret key auth for internal service-to-service communication
        if not internal_secret or internal_secret != settings.INTERNAL_SECRET_KEY:
            return Response({"is_valid": False, "reason": "Unauthorized"}, status=status.HTTP_401_UNAUTHORIZED)

        if not token_id:
            return Response({"is_valid": False, "reason": "Token not provided."}, status=status.HTTP_400_BAD_REQUEST)

        try:
            token = VideoStreamToken.objects.get(pk=token_id)
            if token.is_valid():
                # Mark the token as used to prevent replay attacks
                token.is_used = True
                token.save()
                return Response({"is_valid": True}, status=status.HTTP_200_OK)
            else:
                return Response({"is_valid": False, "reason": "Token is invalid or has expired."}, status=status.HTTP_403_FORBIDDEN)
        except VideoStreamToken.DoesNotExist:
            return Response({"is_valid": False, "reason": "Token not found."}, status=status.HTTP_404_NOT_FOUND)


class CameraServiceListAPIView(APIView):
    """
    API view to retrieve a list of available camera services from settings.
    """
    permission_classes = [IsAdminUser]

    def get(self, request, *args, **kwargs):
        camera_services = getattr(settings, 'CAMERA_SERVICES', {})
        camera_names = list(camera_services.keys())
        return Response(camera_names, status=status.HTTP_200_OK)

class BindCameraToDeviceAPIView(APIView):
    """
    API view to bind a camera service to a device.
    """
    permission_classes = [IsAdminUser]

    def post(self, request, device_id: str, *args, **kwargs):
        camera_service_name = request.data.get('camera_service_name')
        if not camera_service_name:
            return Response(
                {"error": "camera_service_name is required."},
                status=status.HTTP_400_BAD_REQUEST
            )

        try:
            # Step 1: Use the reliable get-then-save pattern.
            device = Device.objects.get(device_id=device_id)
            device.camera_service_name = camera_service_name
            device.save()
            logger.info(f"Successfully saved camera '{camera_service_name}' to device '{device_id}' in database.")

            # Step 2: Broadcast the update via WebSocket, but isolate it to prevent transaction rollback.
            try:
                channel_layer = get_channel_layer()
                
                def device_to_dict(d):
                    return {
                        'id': d.pk,
                        'device_id': d.device_id,
                        'name': d.name,
                        'location': d.location,
                        'status': d.status,
                        'ip_address': d.ip_address,
                        'firmware_version': d.firmware_version,
                        'last_seen': d.last_seen.isoformat() if d.last_seen else None,
                        'created_at': d.created_at.isoformat(),
                        'camera_service_name': d.camera_service_name,
                    }

                async_to_sync(channel_layer.group_send)(
                    'admin_monitor',
                    {
                        'type': 'device_status_update',
                        'payload': device_to_dict(device)
                    }
                )
                logger.info(f"Successfully broadcasted update for device '{device_id}' via WebSocket.")
            except Exception as e:
                # If WebSocket fails, log it, but DO NOT let it affect the HTTP response.
                # The database write is already committed.
                logger.error(f"WebSocket broadcast failed for device {device_id} after saving, but DB change is permanent. Error: {e}", exc_info=True)

            return Response(DeviceSerializer(device).data, status=status.HTTP_200_OK)

        except Device.DoesNotExist:
            return Response(
                {"error": f"Device with ID '{device_id}' not found."},
                status=status.HTTP_404_NOT_FOUND
            )
        except Exception as e:
            logger.error(f"Error binding camera to device '{device_id}': {e}", exc_info=True)
            return Response(
                {"error": "An unexpected error occurred."},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )

class DeviceListAPIView(generics.ListAPIView):
    """
    API view to retrieve a list of all devices.
    """
    queryset = Device.objects.all()
    serializer_class = DeviceSerializer
    permission_classes = [IsAdminUser]

class AccessLogListAPI(generics.ListAPIView):
    """
    API view to retrieve a list of access logs.
    """
    queryset = AccessLog.objects.all().order_by('-timestamp')
    serializer_class = AccessLogSerializer
    permission_classes = [IsAdminUser]

class AccessLogDestroyAPI(generics.DestroyAPIView):
    """
    API view to delete a single access log.
    """
    queryset = AccessLog.objects.all()
    permission_classes = [IsAdminUser]

class AccessLogBatchDeleteAPI(APIView):
    """
    API view to delete multiple access logs in a batch.
    """
    permission_classes = [IsAdminUser]

    def post(self, request, *args, **kwargs):
        log_ids = request.data.get('ids', [])
        if not log_ids or not isinstance(log_ids, list):
            return Response(
                {"error": "Please provide a list of log IDs."},
                status=status.HTTP_400_BAD_REQUEST
            )

        try:
            with transaction.atomic():
                deleted_count, _ = AccessLog.objects.filter(pk__in=log_ids).delete()

            if deleted_count == 0:
                return Response(
                    {"message": "No matching logs found to delete."},
                    status=status.HTTP_404_NOT_FOUND
                )

            return Response(
                {"message": f"Successfully deleted {deleted_count} log(s)."},
                status=status.HTTP_200_OK
            )
        except Exception as e:
            return Response(
                {"error": f"An error occurred during deletion: {str(e)}"},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR
            )
